兩間相隔一條街的畫室,各自接了「聖母領報」這個題材的委託
構圖幾乎一樣,天使、聖母、百合花,連光線角度都相近但兩位畫家彼此不認識,也沒看過對方的畫
簽名的方式,一個簽在畫布左下角、寫全名;一個簽在畫框背面、只留縮寫同一件事,被做了兩次,還用了兩種完全不同的方式標記完工
Day 09 定義了 INotificationChannel,統一了 Email、Sms、Push 的介面
專案後來拆成兩個小隊平行開發,都接到「支援 LINE 官方帳號通知」的需求
但誰都不知道對方也在做
A 小隊寫出來的版本:
public class LineNotifier
{
private readonly ILineApiClient _client;
public LineNotifier(ILineApiClient client) => _client = client;
public void Push(string userId, string text)
{
_client.PushMessage(userId, text);
}
}
B 小隊寫出來的版本:
public class LineMessageSender
{
private readonly ILineApiClient _client;
public LineMessageSender(ILineApiClient client) => _client = client;
public void SendMessage(string to, string content)
{
_client.PushMessage(to, content);
}
}
兩個類別,呼叫同一支底層 API,做的是完全一樣的事
「推送一則 LINE 訊息」
差別只在:一個叫 Push,一個叫 SendMessage
一個參數叫 userId、text,一個叫 to、content
Code Review 時,第三位工程師看到兩個 PR,同時新增了功能重疊的類別,忍不住問:
「所以以後要串 LINE 通知,該用
LineNotifier,還是LineMessageSender?」
沒有人能立刻回答
INotificationChannel,沒辦法跟 EmailNotificationChannel、SmsNotificationChannel 放進同一個列表統一呼叫
答案是:沒有差異,只是兩個人各自簽了不同的名字
這正是異曲同工的類別最典型的成因,不是技術問題,是溝通斷層
兩位畫家不是能力不夠,是根本沒看過彼此的畫布
解法的第一步,不是急著刪掉其中一個,而是先統一介面
兩個類別做的事完全相同,讓它們都遵守 Day 09 已經定義好的 INotificationChannel 契約:
public interface INotificationChannel
{
void Send(Customer customer);
string Preview(Customer customer);
}
一旦介面統一,兩份實作立刻現出原形,它們是同一件事的兩份拷貝:
public class LineNotificationChannel : INotificationChannel
{
private readonly ILineApiClient _client;
public LineNotificationChannel(ILineApiClient client) => _client = client;
public void Send(Customer customer) =>
_client.PushMessage(customer.LineUserId, Preview(customer));
public string Preview(Customer customer) => $"{customer.Name},您的訂單已確認 🎉";
}
LineNotifier 和 LineMessageSender 都可以刪除,換成這一個類別
所有原本呼叫舊版本的地方,改成呼叫 LineNotificationChannel,並且跟 Day 09 的其他管道一樣,被丟進同一個 IEnumerable<INotificationChannel> 裡統一處理
不用再判斷「這次要用哪一個」,因為現在真的只剩一個
不一定。統一介面之後,實作內容可能還有落差:
目標不是「一定只能留一個類別」,是讓所有做同一件事的類別,用同一套語言溝通
異曲同工的類別,最難的不是重構,是發現它存在,因為表面上看起來完全是兩回事
明天我們遇到一種不一樣的困境,不是自己人寫重複了
是別人的函式庫,少了我們需要的那個方法,而我們動不了它的原始碼
模組二最終站:不完整的程式庫類別(Incomplete Library Class)